//
//  Gzip.m
//  YuchBox
//
//  Created by Ivan on 11-10-11.
//  Copyright 2011年 __MyCompanyName__. All rights reserved.
//

#import "Gzip.h"

// to flag the times of function realloc the out buffer 
static NSInteger gzipReallocCounter = 0;
@implementation Gzip

- (id)init
{
    self = [super init];
    if (self) {
        // Initialization code here.
    }
    
    return self;
}

+ (void)zeroGzipReallocCounter
{
    gzipReallocCounter = 0;
}

+ (void)increaseGzipReallocCounter
{
    gzipReallocCounter ++;
}

+ (NSInteger)getGzipReallocCounter
{
    return gzipReallocCounter;
}

// by NSData
+ (Boolean)gzipDeflate2WithDestData:(NSMutableData *)destData offset:(NSInteger)offset outDestLen:(uLong *)outDestLen source:(UInt8 *)source sourceLen:(NSInteger)sourceLen
{
    
    //        +--------------------------------+
    // return |offset | gzip data....          |
    //        +--------------------------------+
    //
    
    [Gzip zeroGzipReallocCounter];
    
    int status;
    z_stream zstream;
    zstream.next_in = source;
    zstream.avail_in = sourceLen;
    zstream.total_out = 0;
    zstream.zalloc = Z_NULL;
    zstream.zfree = Z_NULL;
    
    status = deflateInit2(&zstream, Z_BEST_COMPRESSION, Z_DEFLATED, 15 + 16, 9, Z_DEFAULT_STRATEGY);

    if (Z_OK != status) {
        TTLOGF_ERROR(@"Deflate failed, error code: %d", status);
        return FALSE;
    } else {
        Boolean done = FALSE;
        while (!done) {
            if (zstream.total_out >= [destData length] - offset) {
                // the dest buff has no space
                [Gzip increaseGzipReallocCounter];
                [destData increaseLengthBy:[destData length] / 2];
                
                // after increaseLengthBy call, the memery position maybe change
                //destDataSeeker = [destData mutableBytes];// + GZIP_HEADER_LENGTH;
            }
            zstream.next_out =  [destData mutableBytes] + offset + zstream.total_out;
            zstream.avail_out =  [destData length] - offset - zstream.total_out;
            
            status = deflate(&zstream, Z_FINISH);
            if (Z_STREAM_END == status) {
                // done yes
                done = TRUE;
            } else if (Z_OK != status) {
                TTLOGF_ERROR(@"Deflate error, error code: %d", status);
                return FALSE;
            }
        }
        
        //NSInteger *gzipHeaderLen = (NSInteger *)[destData mutableBytes];
        *outDestLen = zstream.total_out;
        
        deflateEnd(&zstream);
    } 
    return TRUE;
}

// by UInt8
+ (void)gzipDeflate2WithDest:(UInt8 *)dest destLen:(NSInteger)destLen outDestLen:(uLong *)outDestLen source:(UInt8 *)source sourceLen:(NSInteger)sourceLen
{
    [Gzip zeroGzipReallocCounter];
    
    int status;
    z_stream zstream;
    zstream.next_in = source;
    zstream.avail_in = sourceLen;
    zstream.total_out = 0;
    zstream.zalloc = Z_NULL;
    zstream.zfree = Z_NULL;
    
    status = deflateInit2(&zstream, Z_BEST_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY);
    
    if (Z_OK != status) {
        *outDestLen = -1;
        TTLOGF_ERROR(@"Deflate failed, <error code: %d>", status);
        return;
    } else {
        Boolean done = FALSE;
        while (!done) {
            if (zstream.total_out >= destLen) {
                // the dest buff has no space
                [Gzip increaseGzipReallocCounter];
                
                destLen = destLen * 1.5;
                dest = (UInt8 *)realloc(dest, destLen);
//                *outDestLen = -1;
//                TTLOG_ERROR(@"deflate error");
//                return;
                //break;
            }
            
            zstream.next_out = dest + zstream.total_out;
            zstream.avail_out = destLen - zstream.total_out;
            status = deflate(&zstream, Z_FINISH);
            if (Z_STREAM_END == status) {
                done = TRUE;
            } else if (Z_OK != status) {
                *outDestLen = -1;
                TTLOGF_ERROR(@"Deflate error, error code: %d", status);
                //break;
                return;
            } 
        }
        
        *outDestLen = zstream.total_out;
        deflateEnd(&zstream);
    } 
}

+ (void)gzipCompress:(UInt8 *)dest destLen:(NSInteger)destLen outDestLen:(uLong *)outDestLen source:(UInt8 *)source sourceLen:(NSInteger)sourceLen
{
    //bzero(out, 1000);
    
    int ret2 = compress(dest, outDestLen, source, sourceLen);
    
    if (Z_OK != ret2) {
        NSLog(@"Deflate failed %d", ret2);
    }
}

// by NSData
+ (Boolean)gzipinflate2WithDestData:(NSMutableData *)destData offset:(NSInteger)offset outDestLen:(uLong *)outDestLen source:(UInt8 *)source sourceLen:(NSInteger)sourceLen
{
    
    //        +--------------------------------+
    // return |offset | gzip data....          |
    //        +--------------------------------+
    //
    
    [Gzip zeroGzipReallocCounter];
    
    int status;
    z_stream zstream;
    zstream.next_in = source;
    zstream.avail_in = sourceLen;
    zstream.total_out = 0;
    zstream.zalloc = Z_NULL;
    zstream.zfree = Z_NULL;
    
    //status = inflateInit2(&zstream, Z_BEST_COMPRESSION, Z_DEFLATED, 15 + 16, 9, Z_DEFAULT_STRATEGY);
    status = inflateInit2(&zstream, 15 + 16);
    if (Z_OK != status) {
        //NSString *log = [NSString stringWithFormat:];
        TTLOGF_ERROR(@"Inflate init failed, %s <error code: %d>", zError(status), status);
        return FALSE;
    } else {
        Boolean done = FALSE;
        while (!done) {
            if (zstream.total_out >= [destData length] - offset) {
                // the dest buff has no space
                [Gzip increaseGzipReallocCounter];
                [destData increaseLengthBy:[destData length] / 2];
                
                // after increaseLengthBy call, the memery position maybe change
                //destDataSeeker = [destData mutableBytes];// + GZIP_HEADER_LENGTH;
            }
            zstream.next_out =  [destData mutableBytes] + offset + zstream.total_out;
            zstream.avail_out =  [destData length] - offset - zstream.total_out;
            
            status = inflate(&zstream, Z_FINISH);
            if (Z_STREAM_END == status) {
                // done yes
                done = TRUE;
            } else if (Z_OK != status) {
                TTLOGF_ERROR(@"Inflate failed, %s <error code: %d>", zError(status), status);
                return FALSE;
            }
        }
        
        //NSInteger *gzipHeaderLen = (NSInteger *)[destData mutableBytes];
        *outDestLen = zstream.total_out;
        
        deflateEnd(&zstream);
    } 
    return TRUE;
}

+ (void)gzipInflate2WithDest:(UInt8 *)dest destLen:(NSInteger)destLen outDestLen:(uLong *)outDestLen source:(UInt8 *)source sourceLen:(NSInteger)sourceLen
{
    [Gzip zeroGzipReallocCounter];
    
    int status;
    z_stream zstream;
    zstream.next_in = source;
    zstream.avail_in = sourceLen;
    zstream.total_out = 0;
    zstream.zalloc = Z_NULL;
    zstream.zfree = Z_NULL;
    
    status = inflateInit2(&zstream, 15 + 16);
    
    if (Z_OK != status) {
        *outDestLen = -1;
        TTLOGF_ERROR(@"inflate failed, error code: %d", status);
        return;
    } else {
        Boolean done = FALSE;
        while (!done) {
            if (zstream.total_out >= destLen) {
                // the dest buff has no space
                [Gzip increaseGzipReallocCounter];
                
                destLen = destLen * 1.5;
                dest = (UInt8 *)realloc(dest, destLen);
                //                *outDestLen = -1;
                //                TTLOG_ERROR(@"deflate error");
                //                return;
                //break;
            }
            
            zstream.next_out = dest + zstream.total_out;
            zstream.avail_out = destLen - zstream.total_out;
            status = inflate(&zstream, Z_FINISH);
            if (Z_STREAM_END == status) {
                done = TRUE;
            } else if (Z_OK != status) {
                *outDestLen = -1;
                TTLOGF_ERROR(@"inflate error, error code: %d", status);
                //break;
                return;
            } 
        }
        
        *outDestLen = zstream.total_out;
        deflateEnd(&zstream);
    } 

}
@end
